// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/tests/gl_manager.h"

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES2/gl2extchromium.h>

#include <vector>

#include "base/at_exit.h"
#include "base/bind.h"
#include "base/memory/ref_counted_memory.h"
#include "gpu/command_buffer/client/gles2_cmd_helper.h"
#include "gpu/command_buffer/client/gles2_implementation.h"
#include "gpu/command_buffer/client/gles2_lib.h"
#include "gpu/command_buffer/client/transfer_buffer.h"
#include "gpu/command_buffer/common/constants.h"
#include "gpu/command_buffer/common/gles2_cmd_utils.h"
#include "gpu/command_buffer/common/value_state.h"
#include "gpu/command_buffer/service/command_buffer_service.h"
#include "gpu/command_buffer/service/context_group.h"
#include "gpu/command_buffer/service/gl_context_virtual.h"
#include "gpu/command_buffer/service/gles2_cmd_decoder.h"
#include "gpu/command_buffer/service/gpu_scheduler.h"
#include "gpu/command_buffer/service/image_manager.h"
#include "gpu/command_buffer/service/mailbox_manager_impl.h"
#include "gpu/command_buffer/service/memory_tracking.h"
#include "gpu/command_buffer/service/transfer_buffer_manager.h"
#include "gpu/command_buffer/service/valuebuffer_manager.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/gpu_memory_buffer.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_image_ref_counted_memory.h"
#include "ui/gl/gl_share_group.h"
#include "ui/gl/gl_surface.h"

namespace gpu {
namespace {

    class GpuMemoryBufferImpl : public gfx::GpuMemoryBuffer {
    public:
        GpuMemoryBufferImpl(base::RefCountedBytes* bytes,
            const gfx::Size& size,
            gfx::BufferFormat format)
            : bytes_(bytes)
            , size_(size)
            , format_(format)
            , mapped_(false)
        {
        }

        static GpuMemoryBufferImpl* FromClientBuffer(ClientBuffer buffer)
        {
            return reinterpret_cast<GpuMemoryBufferImpl*>(buffer);
        }

        // Overridden from gfx::GpuMemoryBuffer:
        bool Map(void** data) override
        {
            size_t offset = 0;
            size_t num_planes = gfx::NumberOfPlanesForBufferFormat(format_);
            for (size_t i = 0; i < num_planes; ++i) {
                data[i] = reinterpret_cast<uint8*>(&bytes_->data().front()) + offset;
                offset += gfx::RowSizeForBufferFormat(size_.width(), format_, i) * (size_.height() / gfx::SubsamplingFactorForBufferFormat(format_, i));
            }
            mapped_ = true;
            return true;
        }
        void Unmap() override { mapped_ = false; }
        bool IsMapped() const override { return mapped_; }
        gfx::BufferFormat GetFormat() const override { return format_; }
        void GetStride(int* stride) const override
        {
            size_t num_planes = gfx::NumberOfPlanesForBufferFormat(format_);
            for (size_t i = 0; i < num_planes; ++i)
                stride[i] = gfx::RowSizeForBufferFormat(size_.width(), format_, i);
        }
        gfx::GpuMemoryBufferId GetId() const override
        {
            NOTREACHED();
            return gfx::GpuMemoryBufferId(0);
        }
        gfx::GpuMemoryBufferHandle GetHandle() const override
        {
            NOTREACHED();
            return gfx::GpuMemoryBufferHandle();
        }
        ClientBuffer AsClientBuffer() override
        {
            return reinterpret_cast<ClientBuffer>(this);
        }

        base::RefCountedBytes* bytes() { return bytes_.get(); }

    private:
        scoped_refptr<base::RefCountedBytes> bytes_;
        const gfx::Size size_;
        gfx::BufferFormat format_;
        bool mapped_;
    };

} // namespace

int GLManager::use_count_;
scoped_refptr<gfx::GLShareGroup>* GLManager::base_share_group_;
scoped_refptr<gfx::GLSurface>* GLManager::base_surface_;
scoped_refptr<gfx::GLContext>* GLManager::base_context_;

GLManager::Options::Options()
    : size(4, 4)
    , share_group_manager(NULL)
    , share_mailbox_manager(NULL)
    , virtual_manager(NULL)
    , bind_generates_resource(false)
    , lose_context_when_out_of_memory(false)
    , context_lost_allowed(false)
    , context_type(gles2::CONTEXT_TYPE_OPENGLES2)
{
}

GLManager::GLManager()
    : context_lost_allowed_(false)
{
    SetupBaseContext();
}

GLManager::~GLManager()
{
    --use_count_;
    if (!use_count_) {
        if (base_share_group_) {
            delete base_context_;
            base_context_ = NULL;
        }
        if (base_surface_) {
            delete base_surface_;
            base_surface_ = NULL;
        }
        if (base_context_) {
            delete base_context_;
            base_context_ = NULL;
        }
    }
}

// static
scoped_ptr<gfx::GpuMemoryBuffer> GLManager::CreateGpuMemoryBuffer(
    const gfx::Size& size,
    gfx::BufferFormat format)
{
    std::vector<uint8> data(gfx::BufferSizeForBufferFormat(size, format), 0);
    scoped_refptr<base::RefCountedBytes> bytes(new base::RefCountedBytes(data));
    return make_scoped_ptr<gfx::GpuMemoryBuffer>(
        new GpuMemoryBufferImpl(bytes.get(), size, format));
}

void GLManager::Initialize(const GLManager::Options& options)
{
    InitializeWithCommandLine(options, nullptr);
}
void GLManager::InitializeWithCommandLine(const GLManager::Options& options,
    base::CommandLine* command_line)
{
    const int32 kCommandBufferSize = 1024 * 1024;
    const size_t kStartTransferBufferSize = 4 * 1024 * 1024;
    const size_t kMinTransferBufferSize = 1 * 256 * 1024;
    const size_t kMaxTransferBufferSize = 16 * 1024 * 1024;

    context_lost_allowed_ = options.context_lost_allowed;

    gles2::MailboxManager* mailbox_manager = NULL;
    if (options.share_mailbox_manager) {
        mailbox_manager = options.share_mailbox_manager->mailbox_manager();
    } else if (options.share_group_manager) {
        mailbox_manager = options.share_group_manager->mailbox_manager();
    }

    gfx::GLShareGroup* share_group = NULL;
    if (options.share_group_manager) {
        share_group = options.share_group_manager->share_group();
    } else if (options.share_mailbox_manager) {
        share_group = options.share_mailbox_manager->share_group();
    }

    gles2::ContextGroup* context_group = NULL;
    gles2::ShareGroup* client_share_group = NULL;
    if (options.share_group_manager) {
        context_group = options.share_group_manager->decoder_->GetContextGroup();
        client_share_group = options.share_group_manager->gles2_implementation()->share_group();
    }

    gfx::GLContext* real_gl_context = NULL;
    if (options.virtual_manager) {
        real_gl_context = options.virtual_manager->context();
    }

    mailbox_manager_ = mailbox_manager ? mailbox_manager : new gles2::MailboxManagerImpl;
    share_group_ = share_group ? share_group : new gfx::GLShareGroup;

    gfx::GpuPreference gpu_preference(gfx::PreferDiscreteGpu);
    std::vector<int32> attribs;
    gles2::ContextCreationAttribHelper attrib_helper;
    attrib_helper.red_size = 8;
    attrib_helper.green_size = 8;
    attrib_helper.blue_size = 8;
    attrib_helper.alpha_size = 8;
    attrib_helper.depth_size = 16;
    attrib_helper.stencil_size = 8;
    attrib_helper.context_type = options.context_type;

    attrib_helper.Serialize(&attribs);

    DCHECK(!command_line || !context_group);
    if (!context_group) {
        scoped_refptr<gles2::FeatureInfo> feature_info;
        if (command_line)
            feature_info = new gles2::FeatureInfo(*command_line);
        context_group = new gles2::ContextGroup(
            mailbox_manager_.get(), NULL, new gpu::gles2::ShaderTranslatorCache,
            new gpu::gles2::FramebufferCompletenessCache, feature_info, NULL, NULL,
            options.bind_generates_resource);
    }

    decoder_.reset(::gpu::gles2::GLES2Decoder::Create(context_group));

    command_buffer_.reset(new CommandBufferService(
        decoder_->GetContextGroup()->transfer_buffer_manager()));
    ASSERT_TRUE(command_buffer_->Initialize())
        << "could not create command buffer service";

    gpu_scheduler_.reset(new GpuScheduler(command_buffer_.get(),
        decoder_.get(),
        decoder_.get()));

    decoder_->set_engine(gpu_scheduler_.get());

    surface_ = gfx::GLSurface::CreateOffscreenGLSurface(gfx::Size());
    ASSERT_TRUE(surface_.get() != NULL) << "could not create offscreen surface";

    if (base_context_) {
        context_ = scoped_refptr<gfx::GLContext>(new gpu::GLContextVirtual(
            share_group_.get(), base_context_->get(), decoder_->AsWeakPtr()));
        ASSERT_TRUE(context_->Initialize(
            surface_.get(), gfx::PreferIntegratedGpu));
    } else {
        if (real_gl_context) {
            context_ = scoped_refptr<gfx::GLContext>(new gpu::GLContextVirtual(
                share_group_.get(), real_gl_context, decoder_->AsWeakPtr()));
            ASSERT_TRUE(context_->Initialize(
                surface_.get(), gfx::PreferIntegratedGpu));
        } else {
            context_ = gfx::GLContext::CreateGLContext(share_group_.get(),
                surface_.get(),
                gpu_preference);
        }
    }
    ASSERT_TRUE(context_.get() != NULL) << "could not create GL context";

    ASSERT_TRUE(context_->MakeCurrent(surface_.get()));

    if (!decoder_->Initialize(surface_.get(), context_.get(), true, options.size,
            ::gpu::gles2::DisallowedFeatures(), attribs)) {
        return;
    }

    command_buffer_->SetPutOffsetChangeCallback(
        base::Bind(&GLManager::PumpCommands, base::Unretained(this)));
    command_buffer_->SetGetBufferChangeCallback(
        base::Bind(&GLManager::GetBufferChanged, base::Unretained(this)));

    // Create the GLES2 helper, which writes the command buffer protocol.
    gles2_helper_.reset(new gles2::GLES2CmdHelper(command_buffer_.get()));
    ASSERT_TRUE(gles2_helper_->Initialize(kCommandBufferSize));

    // Create a transfer buffer.
    transfer_buffer_.reset(new TransferBuffer(gles2_helper_.get()));

    // Create the object exposing the OpenGL API.
    const bool support_client_side_arrays = true;
    gles2_implementation_.reset(
        new gles2::GLES2Implementation(gles2_helper_.get(),
            client_share_group,
            transfer_buffer_.get(),
            options.bind_generates_resource,
            options.lose_context_when_out_of_memory,
            support_client_side_arrays,
            this));

    ASSERT_TRUE(gles2_implementation_->Initialize(
        kStartTransferBufferSize,
        kMinTransferBufferSize,
        kMaxTransferBufferSize,
        gpu::gles2::GLES2Implementation::kNoLimit))
        << "Could not init GLES2Implementation";

    MakeCurrent();
}

void GLManager::SetupBaseContext()
{
    if (use_count_) {
#if defined(OS_ANDROID)
        base_share_group_ = new scoped_refptr<gfx::GLShareGroup>(
            new gfx::GLShareGroup);
        gfx::Size size(4, 4);
        base_surface_ = new scoped_refptr<gfx::GLSurface>(
            gfx::GLSurface::CreateOffscreenGLSurface(size));
        gfx::GpuPreference gpu_preference(gfx::PreferDiscreteGpu);
        base_context_ = new scoped_refptr<gfx::GLContext>(
            gfx::GLContext::CreateGLContext(base_share_group_->get(),
                base_surface_->get(),
                gpu_preference));
#endif
    }
    ++use_count_;
}

void GLManager::MakeCurrent()
{
    ::gles2::SetGLContext(gles2_implementation_.get());
}

void GLManager::SetSurface(gfx::GLSurface* surface)
{
    decoder_->SetSurface(surface);
}

void GLManager::Destroy()
{
    if (gles2_implementation_.get()) {
        MakeCurrent();
        EXPECT_TRUE(glGetError() == GL_NONE);
        gles2_implementation_->Flush();
        gles2_implementation_.reset();
    }
    transfer_buffer_.reset();
    gles2_helper_.reset();
    command_buffer_.reset();
    if (decoder_.get()) {
        bool have_context = decoder_->GetGLContext() && decoder_->GetGLContext()->MakeCurrent(surface_.get());
        decoder_->Destroy(have_context);
        decoder_.reset();
    }
}

const gpu::gles2::FeatureInfo::Workarounds& GLManager::workarounds() const
{
    return decoder_->GetContextGroup()->feature_info()->workarounds();
}

void GLManager::PumpCommands()
{
    if (!decoder_->MakeCurrent()) {
        command_buffer_->SetContextLostReason(decoder_->GetContextLostReason());
        command_buffer_->SetParseError(::gpu::error::kLostContext);
        return;
    }
    gpu_scheduler_->PutChanged();
    ::gpu::CommandBuffer::State state = command_buffer_->GetLastState();
    if (!context_lost_allowed_) {
        ASSERT_EQ(::gpu::error::kNoError, state.error);
    }
}

bool GLManager::GetBufferChanged(int32 transfer_buffer_id)
{
    return gpu_scheduler_->SetGetBuffer(transfer_buffer_id);
}

Capabilities GLManager::GetCapabilities()
{
    return decoder_->GetCapabilities();
}

int32 GLManager::CreateImage(ClientBuffer buffer,
    size_t width,
    size_t height,
    unsigned internalformat)
{
    GpuMemoryBufferImpl* gpu_memory_buffer = GpuMemoryBufferImpl::FromClientBuffer(buffer);

    scoped_refptr<gfx::GLImageRefCountedMemory> image(
        new gfx::GLImageRefCountedMemory(gfx::Size(width, height),
            internalformat));
    if (!image->Initialize(gpu_memory_buffer->bytes(),
            gpu_memory_buffer->GetFormat())) {
        return -1;
    }

    static int32 next_id = 1;
    int32 new_id = next_id++;

    gpu::gles2::ImageManager* image_manager = decoder_->GetImageManager();
    DCHECK(image_manager);
    image_manager->AddImage(image.get(), new_id);
    return new_id;
}

int32 GLManager::CreateGpuMemoryBufferImage(size_t width,
    size_t height,
    unsigned internalformat,
    unsigned usage)
{
    DCHECK_EQ(usage, static_cast<unsigned>(GL_MAP_CHROMIUM));
    scoped_ptr<gfx::GpuMemoryBuffer> buffer = GLManager::CreateGpuMemoryBuffer(
        gfx::Size(width, height), gfx::BufferFormat::RGBA_8888);
    return CreateImage(buffer->AsClientBuffer(), width, height, internalformat);
}

void GLManager::DestroyImage(int32 id)
{
    gpu::gles2::ImageManager* image_manager = decoder_->GetImageManager();
    DCHECK(image_manager);
    image_manager->RemoveImage(id);
}

uint32 GLManager::InsertSyncPoint()
{
    NOTIMPLEMENTED();
    return 0u;
}

uint32 GLManager::InsertFutureSyncPoint()
{
    NOTIMPLEMENTED();
    return 0u;
}

void GLManager::RetireSyncPoint(uint32 sync_point)
{
    NOTIMPLEMENTED();
}

void GLManager::SignalSyncPoint(uint32 sync_point,
    const base::Closure& callback)
{
    NOTIMPLEMENTED();
}

void GLManager::SignalQuery(uint32 query, const base::Closure& callback)
{
    NOTIMPLEMENTED();
}

void GLManager::SetSurfaceVisible(bool visible)
{
    NOTIMPLEMENTED();
}

uint32 GLManager::CreateStreamTexture(uint32 texture_id)
{
    NOTIMPLEMENTED();
    return 0;
}

void GLManager::SetLock(base::Lock*)
{
    NOTIMPLEMENTED();
}

bool GLManager::IsGpuChannelLost()
{
    NOTIMPLEMENTED();
    return false;
}

gpu::CommandBufferNamespace GLManager::GetNamespaceID() const
{
    return gpu::CommandBufferNamespace::IN_PROCESS;
}

uint64_t GLManager::GetCommandBufferID() const
{
    return 0;
}

} // namespace gpu
